package user

import (
	"runtime"
	"sync"
	"time"
)

var SHARD_COUNT = 32

type UserExMap []*userExMap

type userExMap struct {
	sync.RWMutex
	m map[string]*User
}

func NewUserExMap() UserExMap {
	m := make(UserExMap, SHARD_COUNT)
	for i := 0; i < SHARD_COUNT; i++ {
		m[i] = &userExMap{m: make(map[string]*User)}
	}
	return m
}
func (m UserExMap) getShard(key string) *userExMap {
	return m[uint(fnv32(key))%uint(SHARD_COUNT)]
}

func (m UserExMap) GetAddTime(key string) *time.Time {
	shard := m.getShard(key)
	shard.RLock()
	ul := shard.m[key]
	shard.RUnlock()
	if ul != nil {
		return ul.addTime
	}
	return nil
}

func (m UserExMap) Add(key string, value UserConn) bool {

	shard := m.getShard(key)
	shard.Lock()
	ul := shard.m[key]
	if ul != nil {
		shard.Unlock()
		if value.IsLocal() && !ul.HasLocal() {
			ul.addTime = value.GetCreateTime()
			ul.Add(value)
			return true
		}
		ul.Add(value)
		return false
	} else {
		ulist := NewUser(key)
		shard.m[key] = ulist
		ulist.Add(value)
	}
	shard.Unlock()
	return value.IsLocal()
}

func (m UserExMap) AddM(key string, value interface{}) bool {
	shard := m.getShard(key)
	ul := shard.m[key]
	if ul != nil {
		ul.AddM(value)
	}
	return false
}
func (m UserExMap) GetDelMS(key string) []interface{} {
	shard := m.getShard(key)
	ul := shard.m[key]
	if ul != nil {
		return ul.GetDelMS()
	}
	return nil
}

func (m UserExMap) Delete(key string, value UserConn) (bool, *time.Time) {
	shard := m.getShard(key)
	shard.RLock()
	ul := shard.m[key]
	shard.RUnlock()
	if ul != nil {
		lc := ul.HasLocal()
		ul.Delete(value)
		r, t := m.clearBlankKey(key)
		if !lc {
			return false, value.GetCreateTime()
		}
		if lc && r {
			return true, t
		}
		lcn := ul.HasLocal()
		if lc && lcn {
			return false, value.GetCreateTime()
		}
		if lc && !lcn {
			return true, t
		}
	}
	return value.IsLocal(), value.GetCreateTime()
}

func (m UserExMap) clearBlankKey(key string) (bool, *time.Time) {

	shard := m.getShard(key)

	shard.Lock()
	ul := shard.m[key]
	if ul != nil {
		if ul.Len() == 0 {
			delete(shard.m, key)
			shard.Unlock()
			return true, ul.addTime
		}
	} else {
		delete(shard.m, key)
		shard.Unlock()
		return false, nil
	}
	shard.Unlock()
	return false, ul.addTime
}

func (m UserExMap) DeleteOvalue(key string, value UserConn) {

	shard := m.getShard(key)

	shard.RLock()
	ul := shard.m[key]
	shard.RUnlock()
	if ul != nil {
		ul.Delete(value)
	}

}

func (m UserExMap) DeleteValue(value UserConn) {
	for i := 0; i < SHARD_COUNT; i++ {
		shard := m[i]
		shard.RLock()
		us := make([]string, 0)
		for k, v := range shard.m {
			if v != nil {
				v.Delete(value)
				if v.Len() == 0 {
					us = append(us, k)
				}

			}
		}
		shard.RUnlock()
		shard.Lock()
		for num, v := range us {
			delete(shard.m, v)
			if num>>7<<7 == num {
				runtime.Gosched()
			}
		}
		shard.Unlock()
	}
}

func (m UserExMap) AllLen() int {
	count := 0
	for i := 0; i < SHARD_COUNT; i++ {
		shard := m[i]
		shard.RLock()
		count += len(shard.m)
		shard.RUnlock()
	}
	return count
}

func (m UserExMap) DeleteKey(key string) {

	shard := m.getShard(key)
	shard.Lock()
	delete(shard.m, key)
	shard.Unlock()
}

func (m UserExMap) GetUserConns(key string) []UserConn {
	shard := m.getShard(key)
	shard.RLock()
	ul := shard.m[key]
	if ul != nil {
		us := ul.GetAllArray()
		shard.RUnlock()
		return us
	}
	shard.RUnlock()
	return nil
}

func (m UserExMap) Get(key string) *User {
	shard := m.getShard(key)
	shard.RLock()
	ul := shard.m[key]
	shard.RUnlock()
	return ul
}

func (m UserExMap) GetSetOneValue(key string, value UserConn) (UserConn, bool) {
	shard := m.getShard(key)
	shard.Lock()
	ul := shard.m[key]
	if ul != nil {
		shard.Unlock()
		if ul.Len() > 0 {
			return ul.GetAllArray()[0], false
		}
		ul.Add(value)
		return value, false
	} else {
		ulist := NewUser(key)
		shard.m[key] = ulist
		ulist.Add(value)
		shard.Unlock()
	}
	return value, true
}

func (m UserExMap) Len(key string) int {
	shard := m.getShard(key)
	shard.RLock()
	ul := shard.m[key]
	if ul != nil {
		len := ul.Len()
		shard.RUnlock()
		return len
	}
	shard.RUnlock()
	return 0
}

func (m UserExMap) IsHasKey(key string) bool {
	for i := 0; i < SHARD_COUNT; i++ {
		shard := m[i]
		shard.RLock()
		ul := shard.m[key]
		if ul != nil {
			shard.RUnlock()
			return true
		}
		shard.RUnlock()
	}
	return false
}

func (m UserExMap) GetALLKey() []string {

	count := m.AllLen()
	ch := make(chan string, count)
	go func() {
		wg := sync.WaitGroup{}
		wg.Add(SHARD_COUNT)
		for _, shard := range m {
			go func(shard *userExMap) {
				shard.RLock()
				for key := range shard.m {
					ch <- key
				}
				shard.RUnlock()
				wg.Done()
			}(shard)
		}
		wg.Wait()
		close(ch)
	}()
	keys := make([]string, 0, count)
	for k := range ch {
		keys = append(keys, k)
	}
	return keys
}

func (m UserExMap) GetALLValue() []*User {
	count := m.AllLen()
	ch := make(chan *User, count)
	go func() {
		wg := sync.WaitGroup{}
		wg.Add(SHARD_COUNT)
		for _, shard := range m {
			go func(shard *userExMap) {
				shard.RLock()
				for _, value := range shard.m {
					ch <- value
				}
				shard.RUnlock()
				wg.Done()
			}(shard)
		}
		wg.Wait()
		close(ch)
	}()
	keys := make([]*User, 0, count)
	for k := range ch {
		keys = append(keys, k)
	}
	return keys
}

func (m UserExMap) Items() map[string]interface{} {
	tmp := make(map[string]interface{})
	for item := range m.IterBuffered() {
		tmp[item.Key] = item.Val
	}
	return tmp
}

func (m UserExMap) GetKeyArray(start int, num int) []string {
	data := make([]string, 0)
	var index int = 0
	for i := 0; i < SHARD_COUNT; i++ {
		shard := m[i]
		shard.RLock()
		ln := len(shard.m)
		if ln+index < start {
			index = index + ln
			continue
		}

		for k, _ := range shard.m {
			if index >= start && index < (start+num) {
				data = append(data, k)
			}
			if index >= (start + num) {
				break
			}
			index++
		}
		shard.RUnlock()
	}
	return data
}

func (m UserExMap) GetValueArray(start int, num int) []*User {
	data := make([]*User, 0)
	var index int = 0
	for i := 0; i < SHARD_COUNT; i++ {
		shard := m[i]
		shard.RLock()
		ln := len(shard.m)
		if ln+index < start {
			index = index + ln
			continue
		}

		for _, v := range shard.m {
			if index >= start && index < (start+num) {
				data = append(data, v)
			}
			if index >= (start + num) {
				break
			}
			index++
		}
		shard.RUnlock()
	}
	return data
}


type Tuple struct {
	Key string
	Val interface{}
}

func (m UserExMap) IterBuffered() <-chan Tuple {
	chans := snapshot(m)
	total := 0
	for _, c := range chans {
		total += cap(c)
	}
	ch := make(chan Tuple, total)
	go fanIn(chans, ch)
	return ch
}

func fanIn(chans []chan Tuple, out chan Tuple) {
	wg := sync.WaitGroup{}
	wg.Add(len(chans))
	for _, ch := range chans {
		go func(ch chan Tuple) {
			for t := range ch {
				out <- t
			}
			wg.Done()
		}(ch)
	}
	wg.Wait()
	close(out)
}

func snapshot(m UserExMap) (chans []chan Tuple) {
	chans = make([]chan Tuple, SHARD_COUNT)
	wg := sync.WaitGroup{}
	wg.Add(SHARD_COUNT)
	// Foreach shard.
	for index, shard := range m {
		go func(index int, shard *userExMap) {
			// Foreach key, value pair.
			shard.RLock()
			chans[index] = make(chan Tuple, len(shard.m))
			wg.Done()
			for key, val := range shard.m {
				chans[index] <- Tuple{key, val}
			}
			shard.RUnlock()
			close(chans[index])
		}(index, shard)
	}
	wg.Wait()
	return chans
}

func fnv32(key string) uint32 {
	hash := uint32(2166136261)
	const prime32 = uint32(16777619)
	for i := 0; i < len(key); i++ {
		hash *= prime32
		hash ^= uint32(key[i])
	}
	return hash
}
